﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using RATSharp.Model;
using System.Runtime.Serialization;
using RATSharp.Rest;
using System.Threading.Tasks;
using System.Collections.Generic;
using RATSharp.Settings;


namespace RATSharpW7.ViewModel
{

    /// <summary>
    /// ModelView for SINGLE period.
    /// </summary>
    [DataContract]
    public class PeriodViewModel : SingleObjectViewModel<period, List<period>>
    {
        [DataMember]
        public RATSharp.Model.Object obj { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="period">period to show</param>
        /// <param name="p">periods object. Might not be initialized, in that case use loadObject
        /// method for proper initializaction</param>
        public PeriodViewModel(period period, RATSharp.Model.Object p) : base(period)
        {
            this.obj = p;
        }

        public PeriodViewModel(period period)
            : base(period)
        {
        }

        /// <summary>
        /// load object from server
        /// </summary>
        /// <param name="objectId">objectId</param>
        /// <param name="handler">Rest handler</param>
        public void loadObject(int objectId, EventHandler<LoadObjectEventArgs> handler)
        {
            if (obj == null)
            {
                Task<Objects> task = RestService.getInstance().getObjectById(objectId);
                notifyLoadObjectToView(task, handler);
            }
            else
            {
                LoadObjectEventArgs args = new LoadObjectEventArgs(obj, true);
                handler(this, args);
            }
        }


        protected void notifyLoadObjectToView(Task<Objects> task, EventHandler<LoadObjectEventArgs> handler)
        {
            task.ContinueWith(t =>
            {
                //switch to UI
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    LoadObjectEventArgs args;
                    if (t.Result != null && t.Result.Count >= 1)
                    {
                        args = new LoadObjectEventArgs(t.Result[0], true);
                    }
                    else
                    {
                        args = new LoadObjectEventArgs(null, false);
                    }
                    handler(this, args);
                });
            });
        }

        /// <summary>
        /// submit changed period to server. Changed period might be corrupted 
        /// so in that cae nothing is sent and instand handler is notified with
        /// error message
        /// </summary>
        /// <param name="handler">Rest handler</param>
        public void submitChanges(EventHandler<ModifyReservationEventArgs> handler)
        {
            period period = DataSource;

            DateTime since = period.FromDate;
            DateTime to = period.ToDate;

            //Validate basic stuff and if since date is in the past change it to "now" and move a whole reservation
            //into "future" - which is actually "now"
            bool valid = true;
            string message;
            if (!validateDateTimeBeforeNow(since, out message))
            {
                TimeSpan span = to - since;
                since = DateTime.Now;
                to = since.Add(span);
            }

            valid = validateDates(since, to, out message);
            valid = valid ? validateDateTimeBeforeNow(to, out message) : valid;
            valid = valid ? validateToBeforeSince(since, to, out message) : valid;

            if (!valid)
            {
                ModifyReservationEventArgs args = new ModifyReservationEventArgs(false, message);
                handler(this, args);
            }
            else
            {
                modifyPeriod(handler, period);
            }
        }

        protected virtual void modifyPeriod(EventHandler<ModifyReservationEventArgs> handler, period period)
        {
            //and now we are ready to create reservation
            Reservations reservations = ReservationFactory.createReservations(period.FromDate, period.ToDate,
        period.reservation.PeriodCount, obj, period.pickAction);
            Task<Reservations> task = RestService.getInstance().addReservation(reservations);
            notifyChangeModel(task, handler);
        }

        protected void notifyChangeModel(Task<Reservations> task, EventHandler<ModifyReservationEventArgs> handler)
        {
            task.ContinueWith(t =>
            {
                
                Exception ex = t.Exception;
                //switch to UI
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    if (ex != null || t.Result == null)
                    {
                        handler(this, new ModifyReservationEventArgs(false, ex != null ? ex.Message : "unknown"));
                    }
                    else if (t.Result.Count > 0)
                    {
                        string blockedBy = Application.Current.Resources["errorBlockedPeriod"] as string;
                        handler(this, new ModifyReservationEventArgs(false, blockedBy + " " + t.Result[0].userName));
                    } 
                    else
                    {
                        handler(this, new ModifyReservationEventArgs(true, ""));
                }
                });
            });
        }
        
        



        public bool validateDates(DateTime since, DateTime to, out string errorMessage)
        {
            if (!checkMinimumDistance(since, to))
            {
                errorMessage = (Application.Current.Resources["errorTooShortPeriod"] as string);
                return false;
            }

            if (!checkMaximumDistance(since, to))
            {
                errorMessage = (Application.Current.Resources["errorTooLongPeriod"] as string);
                return false;
            }
            errorMessage = null;
            return true;
        }


        public bool validateToBeforeSince(DateTime since, DateTime to, out string message)
        {
            if (DateTime.Compare(since, to) >= 0)
            {
                message = Application.Current.Resources["errorToBeforeSince"] as string;
                return false;
            }
            message = null;
            return true;
        }


        public bool validateDateBeforeNow(DateTime date, out string message)
        {
            date = date.Date;
            if (DateTime.Compare(date, DateTime.Now.Date) < 0)
            {
                message = Application.Current.Resources["errorDateBeforeNow"] as string;
                return false;
            }
            message = null;
            return true;
        }


        public bool validateDateTimeBeforeNow(DateTime date, out string message)
        {
            if (DateTime.Compare(date, DateTime.Now) <= 0)
            {
                message = Application.Current.Resources["errorDateBeforeNow"] as string;
                return false;
            }
            message = null;
            return true;
        }


        private bool checkMinimumDistance(DateTime since, DateTime to)
        {
            TimeSpan duration = to - since;
            long minimumMinutesDuration = AppSettings.Setting.PropertiesSettings.minimumDuration;
            //TODO: long to int?? wtf?
            return duration.CompareTo(new TimeSpan(0, (int)minimumMinutesDuration, 0)) >= 0;
        }

        private bool checkMaximumDistance(DateTime since, DateTime to)
        {
            TimeSpan duration = to - since;
            long maxDurationInMinutes = AppSettings.Setting.PropertiesSettings.maximumDuration; 
            return duration.CompareTo(new TimeSpan(0,(int) maxDurationInMinutes,0)) <= 0;
        }


        public class LoadObjectEventArgs : EventArgs 
        {
            public RATSharp.Model.Object obj { get; private set; }
            public bool succesfull { get; private set; }
            public LoadObjectEventArgs(RATSharp.Model.Object obj, bool succesfull)
            {
                this.obj = obj;
                this.succesfull = succesfull;
            }
        }

    }
}
